package rsvp.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Vector;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * File related useful functions
 * 
 * @author kuki
 * 
 */
public class FileUtility {

	/**
	 * Outputs the collection into a file list
	 * 
	 * @param directory
	 * @param filter
	 * @param recurse
	 * @return
	 */
	public static File[] listFilesAsArray(File directory,
			FilenameFilter filter, boolean recurse) {
		Collection<File> files = listFiles(directory, filter, recurse);

		File[] arr = new File[files.size()];
		return files.toArray(arr);
	}

	/**
	 * Returns a collection of files from a directory, of a given extension,
	 * recursively or not
	 * 
	 * @param directory
	 * @param filter
	 * @param recurse
	 * 
	 * @return
	 */
	public static Collection<File> listFiles(File directory,
			FilenameFilter filter, boolean recurse) {
		// List of files / directories
		Vector<File> files = new Vector<File>();

		// Get files / directories in the directory
		File[] entries = directory.listFiles();

		// Go over entries
		for (File entry : entries) {
			// If there is no filter or the filter accepts the
			// file / directory, add it to the list
			if (filter == null || filter.accept(directory, entry.getName())) {
				files.add(entry);
			}

			// If the file is a directory and the recurse flag
			// is set, recurse into the directory
			if (recurse && entry.isDirectory()) {
				files.addAll(listFiles(entry, filter, recurse));
			}
		}

		// Return collection of files
		return files;
	}

	/**
	 * @param f
	 * @param strContent
	 * @throws IOException
	 */
	public static void writeToFile(File f, String strContent)
			throws IOException {
//		System.out.println("Writing file: " + f);
		writeToFile(f, strContent, "UTF-8");
	}
	
	/**
	 * Write a complete file, content string is provided
	 * 
	 * @param f
	 * @param strContentUTF_8
	 * @throws IOException
	 */
	public static void writeToFile(File f, String strContent, String sEncoding)
			throws IOException {
		
		f = f.getAbsoluteFile();
		f.getParentFile().mkdirs();
		FileOutputStream fw = new FileOutputStream(f);
		OutputStreamWriter osw = new OutputStreamWriter(fw, sEncoding /* "UTF-8" */);		
		osw.write(strContent);
		osw.close();
	}

	/**
	 * @param source
	 * @param target
	 * @return
	 * @throws IOException
	 */
	public static boolean isDuplicateFileContent(File source, File target)
			throws IOException {

		if (source.length() != target.length())
			return false;

		FileInputStream fin1 = new FileInputStream(source);
		BufferedInputStream bin1 = new BufferedInputStream(fin1);
		FileInputStream fin2 = new FileInputStream(target);
		BufferedInputStream bin2 = new BufferedInputStream(fin2);

		boolean bDuplicate = true;
		int b1 = 0;
		int b2 = 0;

		try {
			while (bDuplicate) {
				b1 = bin1.read();
				b2 = bin2.read();
				if ((-1 == b1) || (-1 == b2))
					break;
				if (b1 != b2)
					bDuplicate = false;
			}
		} finally {
			bin1.close();
			bin2.close();
		}

		return (-1 == b1) && (-1 == b2);
	}

	/**
	 * read UTF-8 content of the file
	 * 
	 * @param f
	 * @return
	 * @throws IOException
	 */
	public static String readFile(File f) throws IOException {
		// System.out.println("Reading file: " + f);
		return readFile(f, "UTF-8");
	}
	
	public static String readFile(File f, String sEncoding) throws IOException
	{
		FileInputStream fr = new FileInputStream(f);
		BufferedInputStream bis = new BufferedInputStream(fr);
		InputStreamReader osr;
		if (sEncoding != null)
			osr = new InputStreamReader(bis, sEncoding);
		else 
			osr = new InputStreamReader(bis);
		

		StringBuffer sb = new StringBuffer();
		int iChar;
		while (-1 != (iChar = osr.read()))
			sb.append((char) iChar);

		osr.close();
		bis.close();
		fr.close();

		String original = sb.toString();
		String roundTrip = original;
		
		return roundTrip;
	}
	
	/**
	 * Returns the number of lines from a file
	 * 
	 * @param f
	 *            
	 * @return number of lines
	 * @throws IOException
	 * @author Octavian.Ene
	 */
	public static long readNumberOfLines(File f) throws IOException {
		FileReader fr = new FileReader(f);
		BufferedReader br = new BufferedReader(fr);
		long iLines = 0;
		while (null != br.readLine())
			iLines++;
		br.close();
		fr.close();
		return iLines;
	}

	/**
	 * @param brCVSInput
	 * 
	 * @return string array with values or null if no more lines are available
	 * @throws IOException
	 */
	public static String[] readCSVLine(BufferedReader brCVSInput)
			throws IOException {
		String sLine = brCVSInput.readLine();
		if (null == sLine || sLine.length() < 2)
			return null;

		StringBuffer sbData = new StringBuffer(sLine);

		// structure of sData is the following:
		// "12/05/2006 15:35:30.484","0.00013225970749347694","9.0909406147267558e-007",...

		ArrayList<String> listOfStrings = new ArrayList<String>();
		String sNextValue;
		do {
			sNextValue = StringUtilities.readCSVLine_extractNextCSV_value(sbData);
			if (sNextValue != null)
				listOfStrings.add(sNextValue);
		} while (sNextValue != null);

		return listOfStrings.toArray(new String[0]);
	}

	

	

	/**
	 * @param sFileName
	 *            sample: "TPC Probe Run: 03/24/08 10:40:19"
	 * 
	 * @return sample: "TPC_Probe_Run-_03-24-08_10-40-19"
	 */
	public static String getSafeFileName(String sFileName) {
		if (null == sFileName)
			return null;

		final String sNewFileName = sFileName.replace(":", "-").replace(
				"%", "_").replace("/", "-").replace(">", "-").replace(
				">", "-").replace("?", "(q)").replace("|", "-");

		// System.out.println(sNewFileName);
		return sNewFileName;
	}

	/**
	 * Deletes a non empty directory
	 * 
	 * @param path
	 * @return
	 */
	static public boolean deleteDirectory(File path) {
		if (path.exists()) {
			File[] files = path.listFiles();
			for (int i = 0; i < files.length; i++) {
				if (files[i].isDirectory()) {
					deleteDirectory(files[i]);
				} else
					files[i].delete();
			}
		}
		return (path.delete());
	}

	/**
	 * Deletes all files with this extension from a directory
	 * 
	 * @param directory
	 * @param extension
	 * @author Octavian.Ene
	 */
	public static void deleteFiles(File directory, String extension) {
		File[] files = directory.listFiles();
		for (int i = 0; i < files.length; i++) {
			String filename = files[i].getAbsolutePath();
			if (extension.equals(filename.substring(
					filename.lastIndexOf('.') + 1, filename.length()))) {
				files[i].delete();
			}
		}
	}

	/**
	 * Zip's source file to destination. Source file is renamed to name within
	 * the destination ZIP archive.
	 * 
	 * @param fInput
	 * @param fZipOut
	 * @param sZipEntryName
	 * @throws IOException
	 */
	public static void zipFile(File fInput, String sZipEntryName, File fZipOut) 
		throws IOException {

		/* create a file stream for the new ZIP file */
		FileOutputStream fileOutputStream = new FileOutputStream(fZipOut);
		JarOutputStream zipOutputStream = new JarOutputStream(fileOutputStream);

		/* open file to add to ZIP file */
		File file = fInput;
		FileInputStream fileInputStream = new FileInputStream(file);

		/* create a buffer and read file into buffer */
		int length = (int) file.length();
		byte[] buffer = new byte[length];
		fileInputStream.read(buffer, 0, length);

		/* create entry in ZIP file */
		JarEntry jarEntry = new JarEntry(sZipEntryName);

		/* add it */
		zipOutputStream.putNextEntry(jarEntry);
		zipOutputStream.write(buffer, 0, length);

		/* clean up and exit */
		fileInputStream.close();
		zipOutputStream.close();
	}

	/**
	 * Copies the <b>source</b> file to the <b>destination</b> file. <br>
	 * If the destination file doesn't exist it is created!
	 * 
	 * @param source
	 * @param destination
	 */
	public static boolean copyFile(File source, File destination) {

		final int F_iBufferSize = 64 * 1024;
		boolean bSuccess = false;

		try {
			if (!destination.exists())
				destination.createNewFile();
		} catch (IOException e1) {
			e1.printStackTrace();
		}

		try {
			FileChannel in = new FileInputStream(source).getChannel();
			FileChannel out = new FileOutputStream(destination).getChannel();
			ByteBuffer buffer = ByteBuffer.allocate(F_iBufferSize);
			while (in.read(buffer) != -1) {
				buffer.flip(); // Prepare for writing
				out.write(buffer);
				buffer.clear(); // Prepare for reading
			}
			in.close();
			out.close();
			bSuccess = true;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		return bSuccess;
	}

	
	
//	/**
//	 * Packs a list of files into a zip archive
//	 * 
//	 * @param fBaseDir
//	 *            - with this argument we make sure that we don't make lots of
//	 *            sub folders
//	 *            
//	 * @param fArInput
//	 * @param fOutZip
//	 * 
//	 * @throws IOException
//	 */
//	public static void zipFiles(File fBaseDir, File[] fArInput, File fOutZip)
//			throws IOException {
//		
//		zipFiles(fBaseDir, fArInput, fOutZip, true);
//	}
	
	/**
	 * Packs a list of files into a zip archive
	 * 
	 * @param fBaseDir
	 *            - with this argument we make sure that we don't make lots of
	 *            sub folders
	 *            
	 * @param fArInput
	 * @param fOutZip
	 * @param bVerbose print to system out
	 * 
	 * @throws IOException
	 */
	public static void zipFiles(File fBaseDir, File[] fArInput, File fOutZip,
			boolean bVerbose) throws IOException {

		final int BUFFER = 2048;
		BufferedInputStream bisOrigin = null;
		FileOutputStream dest = new FileOutputStream(fOutZip);
		ZipOutputStream out = new ZipOutputStream(
				new BufferedOutputStream(dest));

		// out.setMethod(ZipOutputStream.DEFLATED);
		byte data[] = new byte[BUFFER];

		// Zip files
		for (int i = 0; i < fArInput.length; i++) {
			if (bVerbose)
				System.out.println("Adding: " + fArInput[i]);
			if (fArInput[i].isDirectory()) {
				System.out.println("Skiping: " + fArInput[i]);
				continue;
			}
				
			FileInputStream fis = new FileInputStream(fArInput[i]);
			bisOrigin = new BufferedInputStream(fis, BUFFER);
			String sRelativeFilePath = fArInput[i].getAbsolutePath().substring(
					fBaseDir.getAbsolutePath().length());
			ZipEntry entry = new ZipEntry(sRelativeFilePath);
			out.putNextEntry(entry);
			int count;
			while ((count = bisOrigin.read(data, 0, BUFFER)) != -1) {
				out.write(data, 0, count);
			}
			bisOrigin.close();
		}

		out.close();
	}
	

	/**
	 * Returns the size of a directory or file recursively
	 * 
	 * @param dir
	 * @return
	 */
	public static long getDirSize(File dir) {
		long size = 0;
		if (dir.isFile()) {
			size = dir.length();
		} else {
			File[] subFiles = dir.listFiles();

			for (File file : subFiles) {
				if (file.isFile()) {
					size += file.length();
				} else {
					size += getDirSize(file);
				}
			}
		}

		return size;
	}

	/**
	 * @param iLinesToGet
	 * 
	 * @return Gets the first lines from a file
	 * 
	 * @author Kuki 2009-05-26
	 * @throws IOException
	 */
	public static String[] linux_head(File file, int iLinesToGet) throws IOException {

		if (file.isDirectory())
			return null;
		
		BufferedReader br = null;
		String line;
		ArrayList<String> arStrResult = new ArrayList<String>();

		br = new BufferedReader(new FileReader(file));

		while ((line = br.readLine()) != null) {
			if (iLinesToGet <= 0)
				break;
			else
				iLinesToGet--;

			arStrResult.add(line);
		}

		br.close();

		return arStrResult.toArray(new String[0]);
	}

	/**
	 * @param deviceFileName
	 * @param sRegExPattern
	 * 
	 * @return the first line of the match
	 * @throws IOException
	 */
	public static String linux_grepFirst(File file, String sRegExPattern)
			throws IOException {
		
		String[] arStrGrepResult = linux_grep(file, sRegExPattern);
		
		if (arStrGrepResult.length > 0)
			return arStrGrepResult[0];
		else
			return null;
	}
	
	
	/**
	 * @param deviceFileName
	 * @param sRegExPattern
	 * 
	 * @return lines that matches the regular expression pattern
	 * @throws IOException 
	 */
	public static String[] linux_grep(File file, String sRegExPattern) throws IOException {

		Pattern pattern = Pattern.compile(sRegExPattern);
		Matcher matcher = pattern.matcher("");

		ArrayList<String> arStrResult = new ArrayList<String>();
		
		BufferedReader br = null;
		String line;

		br = new BufferedReader(new FileReader(file));

		while ((line = br.readLine()) != null) {
			matcher.reset(line);

			if (matcher.find()) {
				arStrResult.add(line);
			}
		}

		br.close();

		return arStrResult.toArray(new String[0]);
	}
	
	
	

	/**
	 * @param fToCheck
	 * @param sEndingPath
	 * 
	 * @return true if file is ending with path specified
	 */
	public static boolean isFileEndingWithPath(File fToCheck, String sEndingPath) {
		File fEnding = new File(sEndingPath);
		
		do {
			if (null == fToCheck)
				return false;
			
			String sToCheckName = fToCheck.getName();
			String sEndingName = fEnding.getName();
			if (!sToCheckName.equalsIgnoreCase(sEndingName))
				return false;

			fToCheck = fToCheck.getParentFile();
			fEnding = fEnding.getParentFile();
		} while(fEnding != null);
		
		return true;
	}

	

}
